/*
 * Copyright (C) 2008 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package pkg.andru.widget;

import pkg.andru.util.Log;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.PixelFormat;
import android.graphics.PointF;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.ListView;

/**
 * drag and drop listview, need use {@link DragAndDropAdapter}, dont use
 * {@link BaseAdapter}
 * 
 * @author idiottiger
 * 
 */
public class DragAndDropListView extends ListView {

    static final String TAG = "DragAndDropListView";

    /** the drag area width */
    public static final int DRAG_AREA_WIDTH = 64;

    static final int SCROLL_TO_DURATION = 280;

    static final int SCROLL_TOP_BOTTOM_SEP_DISTANCE = 20;

    static final int MODE_NONE = 0, MODE_DRAG = 1;

    static final int INVALID_RESOURCE_ID = -1;

    private ImageView mDragView;
    private WindowManager mWindowManager;
    private WindowManager.LayoutParams mWindowParams;

    private int mDragPos, mSrcDragPos;

    private int mDragPointX, mDragPointY, mXOffset, mYOffset;
    private int mDragItemTop, mDragTopLimit, mDragBottomLimit;
    private int mDragAreaStartX, mDragAreaEndX;
    private int mDragItemBR, mDragItemBC;

    private DrapAndDropListener mListener;
    private int mHeight;

    private Bitmap mDragBitmap;

    private int mItemHeight, mode;

    private PointF mStartPoint = new PointF();
    int[] position = new int[2];

    @SuppressWarnings("rawtypes")
    private DragAndDropAdapter mDragAndDropAdapter;

    private ItemLongClickListener mLongClickListener;

    private int mSavePX, mSavePY;

    public DragAndDropListView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    @SuppressWarnings("rawtypes")
    /**
     * set the drag and drop adpater
     * @param adapter
     */
    public void setDragAndDropAdapter(DragAndDropAdapter adapter) {
        setAdapter(adapter);
        mDragAndDropAdapter = adapter;
    }

    private void init() {
        mode = MODE_NONE;
        setDragItemArea(0, DRAG_AREA_WIDTH);
        setDragItemBackgroundResource(INVALID_RESOURCE_ID);
        setDragItemBackgourndColor(INVALID_RESOURCE_ID);
    }

    /**
     * set the item drag area, if no set, the value is 0 to
     * {@link #DRAG_AREA_WIDTH}
     * 
     * @param px
     *            the x position
     * @param width
     *            the drag area width
     */
    public void setDragItemArea(int px, int width) {
        mDragAreaStartX = px;
        mDragAreaEndX = mDragAreaStartX + width;
    }

    /**
     * set long click trigger the drag
     * 
     * @param trig
     */
    public void setLongClickTriggerDrag(boolean trig) {
        if (trig) {
            if (mLongClickListener == null) {
                mLongClickListener = new ItemLongClickListener();
            }
            setOnItemLongClickListener(mLongClickListener);
        } else {
            setOnItemLongClickListener(null);
        }
    }

    /**
     * set the drag item background resource
     * 
     * @param resid
     */
    public void setDragItemBackgroundResource(int resid) {
        mDragItemBR = resid;
    }

    /**
     * set the drag item background color
     * 
     * @param color
     */
    public void setDragItemBackgourndColor(int color) {
        mDragItemBC = color;
    }

    /**
     * set drag drop listener
     * 
     * @param listener
     */
    public void setDragAndDropListener(DrapAndDropListener listener) {
        mListener = listener;
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (mListener != null && mode != MODE_DRAG) {
            switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                int x = (int) ev.getX();
                int y = (int) ev.getY();

                // get current select item position in total listview
                int itemnum = pointToPosition(x, y);

                if (itemnum == AdapterView.INVALID_POSITION) {
                    break;
                }

                // get the current position
                ViewGroup item = (ViewGroup) getChildAt(itemnum - getFirstVisiblePosition());
                // the drag x and y is releative location in current select item
                mDragPointX = x - item.getLeft();
                mDragPointY = y - item.getTop();
                mXOffset = ((int) ev.getRawX()) - x;
                mYOffset = ((int) ev.getRawY()) - y;

                mSavePX = x;
                mSavePY = y;

                // The left side of the item is the grabber for dragging the
                // item
                if (x >= mDragAreaStartX && x <= mDragAreaEndX) {
                    prepareDrag(item, x, y, itemnum);
                    return false;
                }
                stopDragging();
                break;
            }
        }
        return super.onInterceptTouchEvent(ev);
    }

    private void prepareDrag(View item, int x, int y, int itemnum) {
        item.setDrawingCacheEnabled(true);
        // Create a copy of the drawing cache so that it does not
        // get recycled
        // by the framework when the list tries to clean up memory
        Bitmap bitmap = Bitmap.createBitmap(item.getDrawingCache());
        // reset the cache
        item.setDrawingCacheEnabled(false);
        startDragging(bitmap, x, y);

        // set current drag position
        mDragPos = itemnum;
        mSrcDragPos = mDragPos;
        mHeight = getHeight();

        getLocationOnScreen(position);
        mDragTopLimit = position[1];
        mItemHeight = bitmap.getHeight();
        mDragBottomLimit = mHeight + mDragTopLimit - mItemHeight;

        Log.i(TAG, String.format("mDragPos:%d,mHeight:%d", mDragPos, mHeight));
        Log.i(TAG, String.format("mDragPointX:%d,mDragPointY:%d,mXOffset:%d,mYOffset:%d", mDragPointX, mDragPointY, mXOffset, mYOffset));

        mDragAndDropAdapter.setDragPosition(mDragPos);

        mode = MODE_DRAG;
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        if (mode == MODE_DRAG && mDragView != null) {
            int action = ev.getAction();

            switch (action) {
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                stopDragging();
                // when up get the drop pos
                int dropPos = mDragAndDropAdapter.getDragPosition();
                if (mListener != null && dropPos >= 0 && dropPos < getCount()) {
                    mListener.drop(mSrcDragPos, dropPos);
                }
                mDragAndDropAdapter.setDragPosition(-1);
                mDragAndDropAdapter.notifyDataSetChanged();
                mode = MODE_NONE;
                break;

            case MotionEvent.ACTION_DOWN:
                mStartPoint.set(ev.getX(), ev.getY());
                break;
            case MotionEvent.ACTION_MOVE:

                final int pointerCount = ev.getPointerCount();
                for (int p = 0; p < pointerCount; p++) {
                    int move_y = (int) (ev.getY(p) - mStartPoint.y);

                    int newPosition = mDragItemTop + (int) move_y;
                    if (move_y != 0) {
                        // update the move
                        mWindowParams.y = (move_y < 0) ? Math.max(newPosition, mDragTopLimit) : Math.min(newPosition, mDragBottomLimit);
                        mWindowManager.updateViewLayout(mDragView, mWindowParams);

                        // get current move to where
                        boolean dragDown = move_y > 0;
                        int pos = getCurrentMoveToWhere(mWindowParams.y, dragDown);
                        int oldPos = mDragAndDropAdapter.getDragPosition();
                        if (mListener != null && pos != oldPos) {
                            mListener.drag(oldPos, pos);
                        }
                        mDragAndDropAdapter.setDragMoveToPosition(pos);
                        if (mWindowParams.y == mDragTopLimit || mWindowParams.y == mDragBottomLimit) {
                            int dinstance = mItemHeight / 2 + 3;
                            dinstance = move_y > 0 ? dinstance : -dinstance;
                            smoothScrollBy(dinstance, SCROLL_TO_DURATION);
                        }
                    }
                }

                break;
            }
            return true;
        }
        return super.onTouchEvent(ev);
    }

    // if the top is area of the current item top 1/3 done
    private int getCurrentMoveToWhere(int y, boolean dragDown) {
        int size = getChildCount();
        int num = 0;
        int loopS = dragDown ? size - 1 : 0;
        int loopE = dragDown ? -1 : size;
        for (int i = loopS; i != loopE;) {
            View view = getChildAt(i);
            view.getLocationOnScreen(position);
            int middle = position[1] + mItemHeight / 2;
            if ((dragDown && y + mItemHeight > middle) || (!dragDown && y < middle)) {
                num = i;
                break;
            }
            if (dragDown) {
                i--;
            } else {
                i++;
            }
        }
        return num + getFirstVisiblePosition();
    }

    private void startDragging(Bitmap bm, int x, int y) {
        stopDragging();

        if (mWindowParams == null) {
            mWindowParams = new WindowManager.LayoutParams();

            mWindowParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
            mWindowParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
            mWindowParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
                    | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
            mWindowParams.format = PixelFormat.TRANSLUCENT;
            mWindowParams.windowAnimations = 0;
        }
        mWindowParams.x = x - mDragPointX + mXOffset;
        mWindowParams.y = y - mDragPointY + mYOffset;
        mWindowParams.gravity = Gravity.TOP | Gravity.LEFT;

        // the y mean the current item's top in screen
        Log.i(TAG, "startDragging[x=" + mWindowParams.x + ",y=" + mWindowParams.y + "]");

        mDragItemTop = mWindowParams.y;

        Context context = getContext();
        ImageView v = new ImageView(context);
        if (mDragItemBR != INVALID_RESOURCE_ID) {
            v.setBackgroundResource(mDragItemBR);
        }
        if (mDragItemBC != INVALID_RESOURCE_ID) {
            v.setBackgroundColor(mDragItemBC);
        }
        v.setPadding(2, 2, 2, 2);
        v.setImageBitmap(bm);
        mDragBitmap = bm;

        // get the current select item and show a flow image
        mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        mWindowManager.addView(v, mWindowParams);
        mDragView = v;
    }

    private void stopDragging() {
        if (mDragView != null) {
            mDragView.setVisibility(GONE);
            WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
            wm.removeView(mDragView);
            mDragView.setImageDrawable(null);
            mDragView = null;
        }
        if (mDragBitmap != null) {
            mDragBitmap.recycle();
            mDragBitmap = null;
        }
    }

    class ItemLongClickListener implements AdapterView.OnItemLongClickListener {

        @Override
        public boolean onItemLongClick(AdapterView<?> arg0, View view, int pos, long arg3) {
            prepareDrag(view, mSavePX, mSavePY, pos);
            mStartPoint.set(mSavePX, mSavePY);
            return true;
        }

    }

    /**
     * 
     * @author idiottiger
     * 
     */
    public static interface DrapAndDropListener {
        /**
         * when drag begin and move, will callback
         * 
         * @param from
         * @param to
         */
        public void drag(int from, int to);

        /**
         * when drag finish, and drop, the drag start pos and drop end will pass
         * here
         * 
         * @param from
         * @param to
         */
        public void drop(int from, int to);
    }
}
